/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2021-2021.
 * Description: Support for skiping showing pte when specific undefinstr happened.
 * Author: hanzongcheng
 * Create: 2021-10-12
 */
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/sysctl.h>
#include <linux/uaccess.h>
#include <linux/bitops.h>
#include <linux/signal.h>
#include <linux/rtos_undefinstr_enhance_filter.h>

#ifdef CONFIG_RTOS_DEBUG_UNDEFINSTR_ENHANCE_FILTER
static char undefinstr_enhance_filter[UNDEFINSTR_FILTER_MAX_SIZE];
static unsigned long masked_undefinstr;
static int masked_signal;
static unsigned int undefinstr_bit_size;

bool skip_dump_info(unsigned long pc, int sig)
{
	u32 val;
	bool ret = false;

	if (sig != masked_signal)
		return ret;
	if (undefinstr_bit_size == 0)
		return ret;
	if (copy_from_user(&val, (unsigned long *)pc, sizeof(val))) {
		pr_info("undefinstr_filter: Failed to copy from user, continue to dump page table\n");
		return ret;
	}
	val = cpu_to_le32(val) & ((1UL << undefinstr_bit_size) - 1);
	if (val == masked_undefinstr)
		ret = true;
	return ret;
}

static int parse_undefinstr_filter(const char *buffer)
{
	unsigned long tmp_undefinstr;
	unsigned int tmp_signal;
	char *startptr = NULL;
	char *endptr = NULL;

	if (*buffer == '\n') {
		masked_undefinstr = 0;
		masked_signal = 0;
		undefinstr_bit_size = 0;
		return 0;
	}

	tmp_undefinstr = simple_strtoul(buffer, &endptr, 16);
	if (*endptr != ',') {
		pr_err("undefinstr_filter: Error, the separator must be a comma.\n");
		return -EINVAL;
	}
	startptr = endptr + 1;
	tmp_signal = simple_strtoul(startptr, &endptr, 10);
	if (!valid_signal(tmp_signal) || (*endptr != '\0' && *endptr != '\n')) {
		pr_err("undefinstr_filter: Error, invalid signal.\n");
		return -EINVAL;
	}
	masked_undefinstr = tmp_undefinstr;
	masked_signal = tmp_signal;
	undefinstr_bit_size = fls(tmp_undefinstr);
	return 0;
}

static int proc_dostring_undefinstr_filter(struct ctl_table *table, int write,
					   void *buffer, size_t *lenp, loff_t *ppos)
{
	int error;

	if (write) {
		error = parse_undefinstr_filter(buffer);
		if (error)
			return error;
	}
	return proc_dostring(table, write, buffer, lenp, ppos);
}
#endif /* CONFIG_RTOS_DEBUG_UNDEFINSTR_ENHANCE_FILTER */

int show_exception_enhance;
static struct ctl_table undefinstr_filter_table[] = {
	{
		.procname       = "undefinstr_enhance_enable",
		.data           = &show_exception_enhance,
		.maxlen         = sizeof(int),
		.mode           = 0640,
		.proc_handler   = proc_dointvec_minmax,
		.extra1         = SYSCTL_ZERO,
		.extra2         = SYSCTL_ONE,
	},
#ifdef CONFIG_RTOS_DEBUG_UNDEFINSTR_ENHANCE_FILTER
	{
		.procname       = "undefinstr_enhance_filter",
		.data           = &undefinstr_enhance_filter,
		.maxlen         = UNDEFINSTR_FILTER_MAX_SIZE,
		.mode           = 0640,
		.proc_handler   = proc_dostring_undefinstr_filter,
	},
#endif
	{ }
};

static int __init undefinstr_filter_sysctl_init(void)
{
	if (!register_sysctl("kernel", undefinstr_filter_table))
		return -EINVAL;
	return 0;
}

module_init(undefinstr_filter_sysctl_init);
